package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        //查询sku对应的平台属性
        List<BaseAttrInfo> baseAttrInfoList =  productFeignClient.getAttrList(skuId);
        if(null != baseAttrInfoList) {
            List<SearchAttr> searchAttrList =  baseAttrInfoList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                //一个sku只对应一个属性值
                List<BaseAttrValue> baseAttrValueList = baseAttrInfo.getAttrValueList();
                searchAttr.setAttrValue(baseAttrValueList.get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());

            goods.setAttrs(searchAttrList);
        }

        //查询sku信息
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        // 查询品牌
        BaseTrademark baseTrademark = productFeignClient.getTrademark(skuInfo.getTmId());
        if (baseTrademark != null){
            goods.setTmId(skuInfo.getTmId());
            goods.setTmName(baseTrademark.getTmName());
            goods.setTmLogoUrl(baseTrademark.getLogoUrl());

        }

        // 查询分类
        BaseCategoryView baseCategoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        if (baseCategoryView != null) {
            goods.setCategory1Id(baseCategoryView.getCategory1Id());
            goods.setCategory1Name(baseCategoryView.getCategory1Name());
            goods.setCategory2Id(baseCategoryView.getCategory2Id());
            goods.setCategory2Name(baseCategoryView.getCategory2Name());
            goods.setCategory3Id(baseCategoryView.getCategory3Id());
            goods.setCategory3Name(baseCategoryView.getCategory3Name());
        }

        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setPrice(skuInfo.getPrice().doubleValue());
        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName());
        goods.setCreateTime(new Date());

        this.goodsRepository.save(goods);

    }

    @Override
    public void lowerGoods(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        // 定义key
        String hotKey = "hotScore";
        // 每访问一次skuid, 往redis加1
        Double hotScore = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        // 每10次往es加一次
        if (hotScore % 10 == 0) {
            Optional<Goods> goodsOptional = goodsRepository.findById(skuId);
            Goods goods = goodsOptional.get();
            goods.setHotScore(hotScore.longValue());
            goodsRepository.save(goods);
        }
    }

    // 检索的商品列表页面
    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
//        1.  根据用户的检索条件生成对应的dsl语句 {方法}
        SearchRequest searchRequest = buildQueryDsl(searchParam);
//        2.  执行dsl 语句并获取到结果集{如何操作es，引入客户端}
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//        3.  将查询出来的结果集进行封装{方法}
        SearchResponseVo searchResponseVo = parseSearchResult(searchResponse);
        searchResponseVo.setPageNo(searchParam.getPageNo()); // 1
        searchResponseVo.setPageSize(searchParam.getPageSize()); // 3
        //  总数%每页显示的条数==0?总数/每页显示的条数:总数/每页显示的条数+1
        long totalPages = (searchResponseVo.getTotal()+searchResponseVo.getPageSize()-1)/searchResponseVo.getPageSize();
        searchResponseVo.setTotalPages(totalPages); // 总页数
        return searchResponseVo;
    }

    /**
     * 3.  将查询出来的结果集进行封装{方法}
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        /**
         * //品牌 此时vo对象中的id字段保留（不用写） name就是“品牌” value: [{id:100,name:华为,logo:xxx},{id:101,name:小米,log:yyy}]
         *     private List<SearchResponseTmVo> trademarkList;
         *     //所有商品的顶头显示的筛选属性
         *     private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
         *
         *     //检索出来的商品信息
         *     private List<Goods> goodsList = new ArrayList<>();
         *
         *     private Long total;//总记录数
         *     private Integer pageSize;//每页显示的内容
         *     private Integer pageNo;//当前页面
         *     private Long totalPages;
         *
         */
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //  品牌数据从聚合中获取！
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();

        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  获取品牌Id
            String keyAsString = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(keyAsString));
            //  获取到品牌名称
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);
            //  获取到品牌的URL
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            //  返回当前对象
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        //  设置品牌数据
        searchResponseVo.setTrademarkList(trademarkList);

        //  获取平台属性集合：聚合 attrAgg --- nested
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            Number keyAsNumber = ((Terms.Bucket) bucket).getKeyAsNumber();
            //  attrId
            searchResponseAttrVo.setAttrId(keyAsNumber.longValue());
            //  attrName
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            //  attrNameAgg.getBuckets().get(0).getKeyAsString() == "key" : "价格",
            searchResponseAttrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());
            //  attrValueList
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            //  获取平台属性值集合
            List<String> stringList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            //  赋值平台属性值集合
            searchResponseAttrVo.setAttrValueList(stringList);
            //  返回数据
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //  赋值平台属性集合
        searchResponseVo.setAttrsList(attrsList);

        SearchHits hits = searchResponse.getHits();
        //  获取内层hits
        SearchHit[] subHits = hits.getHits();

        //  创建一个goodsList 集合
        List<Goods> goodsList = new ArrayList<>();
        //  赋值goodsList;
        if (subHits!=null && subHits.length>0){
            //  循环遍历
            for (SearchHit subHit : subHits) {
                //  sourceAsString 这个是json 数据格式
                String sourceAsString = subHit.getSourceAsString();
                //  将其转换为java 对象 goods的名称没有高亮显示！
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                // subHit.getHighlightFields().get("title")是null,因为3级分类id检索, 是没有高亮的
                System.out.println("=============================");
                System.out.println(subHit.getHighlightFields()); //{}
                System.out.println(subHit.getHighlightFields().get("title")); // null
                System.out.println("=============================");
                if (subHit.getHighlightFields().get("title") != null && subHit.getHighlightFields().get("title").getFragments()[0] != null) {
                   //  获取到高亮中的数据
                   Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                   goods.setTitle(title.toString());
               }
                //  如果有高亮，就是高亮的名称！
                goodsList.add(goods);
            }
        }
        //  赋值goodsList集合
        searchResponseVo.setGoodsList(goodsList);
        //  获取到总条数
        searchResponseVo.setTotal(hits.totalHits);

        return searchResponseVo;
    }

    /**
     * 1.  根据用户的检索条件生成对应的dsl语句 {方法}
     * @param searchParam
     * @return
     */
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        // 查询条件构建器, 相当于{}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //bool
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //搜索关键词title
        if (!StringUtils.isEmpty(searchParam.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
        }
        //分类id条件
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }
        //品牌id条件 trademark=2:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)){
            //  进行分割 坑：注意如果你使用的是工具类进行分割的话，不要使用spring框架的！ StringUtils.split()
            String[] split = trademark.split(":");
            if (split!=null && split.length==2){
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }

        }
        //平台属性值 props=23:8G:运行内存33&props=107:华为:二级手机
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0) {
            for (String prop : props) {
                String[] split = prop.split(":");
                if (split.length == 3){
                    //  split[0]; 属性Id
                    //  split[1]; 属性值名称
                    //  创建两个boolQuery
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    // 嵌套查询子查询
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    //  设置平台属性Id
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    //  设置平台属性值名称
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    //  bool --- must --- nested
                    boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));
                    //  将boolQuery 放入最外层
                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }
        //query
        searchSourceBuilder.query(boolQueryBuilder);

        //  分页 10 条数据 0,3  3,3  6,3
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize());//size=3

        //排序 order=1:asc order=1:desc
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //  声明一个字段
            String field = "";
            //  分割
            String[] split = order.split(":");
            if (split!=null && split.length==2){
                switch (split[0]){
                    case "1":
                        field="hotScore";
                        break;
                    case "2":
                        field="price";
                        break;
                }
                searchSourceBuilder.sort(field,"asc".equals(split[1])?SortOrder.ASC: SortOrder.DESC);
            } else {
                //  不走if 给默认值！
                searchSourceBuilder.sort("hotScore",SortOrder.DESC);
            }
        }

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        // 品牌id聚合->品牌名字+品牌url
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"));
        searchSourceBuilder.aggregation(aggregationBuilder);

        // 平台属性值聚合
        NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("attrAgg", "attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue")));

        searchSourceBuilder.aggregation(nestedAggregationBuilder);
        //  设置哪些字段显示，哪些字段不显示！
        searchSourceBuilder.fetchSource(new String []{"id","title","defaultImg","price"},null);
        //  searchSourceBuilder 构建完成！

        //  GET /goods/info/_search
        SearchRequest searchRequest = new SearchRequest("goods").types("info");
        searchRequest.source(searchSourceBuilder);

        //  dsl 语句在哪 searchSourceBuilder
        String dsl = searchSourceBuilder.toString();
        System.out.println("DSL:\t"+dsl);

        return searchRequest;
    }
}